import Axios, { type AxiosInstance, type AxiosRequestConfig } from "axios";
import type {
  PureHttpError,
  RequestMethods,
  PureHttpResponse,
  PureHttpRequestConfig
} from "./types.d";
import { stringify } from "qs";
import NProgress from "../progress";
import { getToken } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user";
const { VITE_API_BASE_URL } = import.meta.env;

// 相关配置请参考：www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = {
  // 请求超时时间
  timeout: 30000,
  baseURL: VITE_API_BASE_URL,
  headers: {
    Accept: "application/json, text/plain, */*",
    "Content-Type": "application/json",
    "X-Requested-With": "XMLHttpRequest"
  },
  // 数组格式参数序列化（https://github.com/axios/axios/issues/5142）
  paramsSerializer: {
    serialize: params => stringify(params, { arrayFormat: "repeat" })
  }
};

class PureHttp {
  constructor() {
    this.httpInterceptorsRequest();
    this.httpInterceptorsResponse();
  }

  /** token过期后，暂存待执行的请求 */
  private static requests = [];

  /** 防止重复刷新token */
  private static isRefreshing = false;

  /** 初始化配置对象 */
  private static initConfig: PureHttpRequestConfig<any> = {};

  /** 保存当前Axios实例对象 */
  private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);

  /** 重连原始请求 */
  private static retryOriginalRequest(config: PureHttpRequestConfig<any>) {
    return new Promise(resolve => {
      PureHttp.requests.push((key: string, token: string) => {
        config.headers[key] = token;
        resolve(config);
      });
    });
  }

  /** 请求拦截 */
  private httpInterceptorsRequest(): void {
    PureHttp.axiosInstance.interceptors.request.use(
      async (config: PureHttpRequestConfig<any>): Promise<any> => {
        // 开启进度条动画
        NProgress.start();
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof config.beforeRequestCallback === "function") {
          config.beforeRequestCallback(config);
          return config;
        }
        if (PureHttp.initConfig.beforeRequestCallback) {
          PureHttp.initConfig.beforeRequestCallback(config);
          return config;
        }
        /** 请求白名单，放置一些不需要token的接口（通过设置请求白名单，防止token过期后再请求造成的死循环问题） */
        const whiteList = [
          "/auth/refreshToken",
          "/auth/securityLogin",
          "/auth/logout"
        ];
        return new Promise(resolve => {
          const data = getToken();
          if (data) {
            const now = new Date().getTime();
            const expired = parseInt(data.expires) - now <= 0;
            const isWhite = whiteList.includes(config.url);
            if (expired && !isWhite) {
              if (!PureHttp.isRefreshing) {
                PureHttp.isRefreshing = true;
                // token过期刷新
                useUserStoreHook()
                  .handRefreshToken({ refreshToken: data.refreshToken })
                  .then(res => {
                    const token = res.accessToken;
                    const key = res.tokenKey;
                    config.headers[data.tokenKey] = data.accessToken;
                    PureHttp.requests.forEach(cb => cb(key, token));
                    PureHttp.requests = [];
                  })
                  .catch(() => {
                    useUserStoreHook().logOut();
                  })
                  .finally(() => {
                    PureHttp.isRefreshing = false;
                  });
              }
              resolve(PureHttp.retryOriginalRequest(config));
            } else {
              config.headers[data.tokenKey] = data.accessToken;
              resolve(config);
            }
          } else {
            resolve(config);
          }
        });
      },
      error => {
        return Promise.reject(error);
      }
    );
  }

  /** 响应拦截 */
  private httpInterceptorsResponse(): void {
    const instance = PureHttp.axiosInstance;
    instance.interceptors.response.use(
      (response: PureHttpResponse<any>) => {
        const $config = response.config;
        // 关闭进度条动画
        NProgress.done();
        const res = response.data;
        if (!res.success) {
          return Promise.reject(new Error(res.msg));
        }
        // 优先判断post/get等方法是否传入回调，否则执行初始化设置等回调
        if (typeof $config.beforeResponseCallback === "function") {
          $config.beforeResponseCallback(response);
          return res.data;
        }
        if (PureHttp.initConfig.beforeResponseCallback) {
          PureHttp.initConfig.beforeResponseCallback(response);
          return res.data;
        }
        return res.data;
      },
      (error: PureHttpError) => {
        const $error = error;
        $error.isCancelRequest = Axios.isCancel($error);
        // 关闭进度条动画
        NProgress.done();
        if (error.message.includes("timeout")) {
          return Promise.reject("连接超时");
        }
        if (error.response.status === 401) {
          useUserStoreHook().logOut();
        }
        // 所有的响应异常 区分来源为取消请求/非取消请求
        return Promise.reject($error);
      }
    );
  }

  /** 通用请求工具函数 */
  public request<T>(
    method: RequestMethods,
    url: string,
    params?: any,
    axiosConfig?: PureHttpRequestConfig<T>
  ): Promise<T> {
    const config = {
      method,
      url,
      params,
      ...axiosConfig
    } as PureHttpRequestConfig<T>;
    // 单独处理自定义请求/响应回调
    return new Promise((resolve, reject) => {
      PureHttp.axiosInstance
        .request(config)
        .then((response: any) => {
          resolve(response);
        })
        .catch(error => {
          reject(error);
        });
    });
  }

  /** 单独抽离的post工具函数 */
  public post<T>(
    url: string,
    params?: any,
    config?: PureHttpRequestConfig<T>
  ): Promise<T> {
    return this.request<T>("post", url, params, config);
  }

  /** 单独抽离的get工具函数 */
  public get<T>(
    url: string,
    params?: any,
    config?: PureHttpRequestConfig<T>
  ): Promise<T> {
    return this.request<T>("get", url, params, config);
  }

  public upload<P>(
    url?: string,
    params?: any,
    data?: FormData,
    config?: PureHttpRequestConfig<P>
  ) {
    return this.request<P>("post", url, params, {
      headers: { "Content-Type": "multipart/form-data" },
      data,
      ...config
    });
  }
}

export const http = new PureHttp();
